//Copyright 2009 Dominik Punz
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.

#ifndef jniModel_h__153804
#define jniModel_h__153804

#include "jniEnvironment.h"
#include "jniReference.h"
#include <string>
#include <cassert>

typedef int va_list_dummy;
#define VARGS 0

class JClass
{
public:
  JClass(const std::string& fullyQualifiedName) : mstrFullyQualifiedName(fullyQualifiedName), mClazz(0) 
  { 
    mClazz = mJni.Env()->FindClass(mstrFullyQualifiedName.c_str());
    assert(mClazz);
  }

  jclass Class()
  {
    return mClazz.Get();
  }

  bool IsInstance(jobject jobj)
  {
    return mJni.Env()->IsInstanceOf(jobj, Class()) == JNI_TRUE;
  }

private:
  JClass();
  std::string mstrFullyQualifiedName;
  CJniLocalReference<jclass> mClazz;
  CJniEnvironment mJni;
};

class JMethod
{
public:
  JMethod(JClass* javaClass, const std::string& methodName, const std::string& signature) : mpJavaClass(javaClass), mstrMethodName(methodName), mstrSignature(signature), mMethodId(0) 
  { }

  jmethodID MethodID()
  {
    if (!mMethodId)
    {
      mMethodId = RetrieveMethodId(mstrMethodName.c_str(), mstrSignature.c_str());
      assert(mMethodId);
    }
    return mMethodId;
  }

  jclass Class()
  {
    return mpJavaClass->Class();
  }

protected:
  virtual jmethodID RetrieveMethodId(const std::string& methodName, const std::string& signature) = 0;

private:
  JMethod();
  std::string mstrMethodName;
  std::string mstrSignature;
  JClass* mpJavaClass;
  jmethodID mMethodId;
};

class JConstructor : public JMethod
{
public:
  JConstructor(JClass* javaClass, const std::string& signature) :  JMethod(javaClass, "<init>", signature) { }
  virtual jobject New(va_list_dummy start, ...)
  {
    va_list params;
    va_start(params, start);
    CJniEnvironment jni;
    CJniLocalReference<jobject> result = jni.Env()->AllocObject(Class());
    if (result) {
      jni.Env()->CallNonvirtualVoidMethodV(result.Get(), Class(), MethodID(), params);
      if (jni.Env()->ExceptionCheck()) 
      {
        result.Release();
      }
    }
    va_end(params);
    return result.Detach();
  }

protected:
  jmethodID RetrieveMethodId(const std::string& methodName, const std::string& signature)
  {
    CJniEnvironment jni;
    return jni.Env()->GetMethodID(Class(), methodName.c_str(), signature.c_str());
  }
};

template<typename _ReturnType>
class JMethodI : public JMethod
{
public:
  typedef _ReturnType TReturnType;
  JMethodI(JClass* javaClass, const std::string& methodName, const std::string& signature) :  JMethod(javaClass, methodName, signature) { }
  virtual TReturnType Invoke(jobject instance, ...) = 0;

protected:
  jmethodID RetrieveMethodId(const std::string& methodName, const std::string& signature)
  {
    CJniEnvironment jni;
    return jni.Env()->GetMethodID(Class(), methodName.c_str(), signature.c_str());
  }
};

template<typename _ReturnType>
class JMethodS : public JMethod
{
public:
  typedef _ReturnType TReturnType;
  JMethodS(JClass* javaClass, const std::string& methodName, const std::string& signature) :  JMethod(javaClass, methodName, signature) { }
  virtual TReturnType StaticInvoke(va_list_dummy start, ...) = 0;

protected:
  jmethodID RetrieveMethodId(const std::string& methodName, const std::string& signature)
  {
    CJniEnvironment jni;
    return jni.Env()->GetStaticMethodID(Class(), methodName.c_str(), signature.c_str());
  }
};

class JIntMethodI : public JMethodI<jint>
{
public:
  JIntMethodI(JClass* javaClass, const std::string& methodName, const std::string& signature) :  JMethodI<TReturnType>(javaClass, methodName, signature) { }
  virtual TReturnType Invoke(jobject instance, ...)
  {
    va_list params;
    va_start(params, instance);
    CJniEnvironment jni;
    TReturnType result = jni.Env()->CallIntMethodV(instance, MethodID(), params);
    va_end(params);
    return result;
  }
};

class JObjectMethodI : public JMethodI<jobject>
{
public:
  JObjectMethodI(JClass* javaClass, const std::string& methodName, const std::string& signature) :  JMethodI<TReturnType>(javaClass, methodName, signature) { }
  virtual TReturnType Invoke(jobject instance, ...)
  {
    va_list params;
    va_start(params, instance);
    CJniEnvironment jni;
    TReturnType result = jni.Env()->CallObjectMethodV(instance, MethodID(), params);
    va_end(params);
    return result;
  }
};

class JBooleanMethodI : public JMethodI<jboolean>
{
public:
  JBooleanMethodI(JClass* javaClass, const std::string& methodName, const std::string& signature) :  JMethodI<TReturnType>(javaClass, methodName, signature) { }
  virtual TReturnType Invoke(jobject instance, ...)
  {
    va_list params;
    va_start(params, instance);
    CJniEnvironment jni;
    TReturnType result = jni.Env()->CallBooleanMethodV(instance, MethodID(), params);
    va_end(params);
    return result;
  }
};

class JVoidMethodI : public JMethodI<void>
{
public:
  JVoidMethodI(JClass* javaClass, const std::string& methodName, const std::string& signature) :  JMethodI<TReturnType>(javaClass, methodName, signature) { }
  virtual TReturnType Invoke(jobject instance, ...)
  {
    va_list params;
    va_start(params, instance);
    CJniEnvironment jni;
    jni.Env()->CallVoidMethodV(instance, MethodID(), params);
    va_end(params);
  }
};

class JObjectMethodS : public JMethodS<jobject>
{
public:
  JObjectMethodS(JClass* javaClass, const std::string& methodName, const std::string& signature) :  JMethodS<TReturnType>(javaClass, methodName, signature) { }
  virtual TReturnType StaticInvoke(va_list_dummy start, ...)
  {
    va_list params;
    va_start(params, start);
    CJniEnvironment jni;
    TReturnType result = jni.Env()->CallStaticObjectMethodV(Class(), MethodID(), params);
    va_end(params);
    return result;
  }
};

#endif // jniModel_h__153804